package acme.serviceproviders.webservices;

import java.util.*;

import javax.jws.*;
import org.hibernate.*;

import acme.serviceproviders.*;
import acme.serviceproviders.data.*;
import acme.serviceproviders.data.utility.*;

import javax.jws.WebService;

@WebService
public class ApartmentService {

	private final long APARTMENT_PROVIDER_ACCOUNT = 33333333;
	
	@WebMethod
	public Apartment[] getApartments(Date moveInDate, Date moveOutDate, String city)
		throws DateIncorrectException 
	{
		if (moveInDate.after(moveOutDate))
			throw new DateIncorrectException("MoveIn Date is later then MoveOut Date");
		
		if (moveInDate.before(Calendar.getInstance().getTime()))
			throw new DateIncorrectException("MoveIn Date has already passed");		
		
		Session session = HibernateUtility.getSessionFactory().openSession();
		
		try
		{
		
		List<Apartment> apartments = session.createQuery(
				"select a from Apartment a where a.city = :city AND a.apartmentId NOT IN(SELECT b.Apartment.apartmentId FROM ApartmentBooking b WHERE (b.bookedFrom BETWEEN :startDate and :endDate) OR (b.bookedTill BETWEEN :startDate and :endDate) OR ((b.bookedFrom < :startDate) AND (b.bookedTill > :endDate)))")
			.setString("city", city)
			.setDate("startDate", moveInDate)
			.setDate("endDate", moveOutDate)
			.list();
		
		Apartment[] result = new Apartment[apartments.size()];
		apartments.toArray(result);
		
		return result;
		}
		finally
		{
			session.close();			
		}
	}
	
	@WebMethod
	public String bookApartment(int apartmentId, Date moveInDate, Date moveOutDate, long accountNumber)
		throws DateIncorrectException, ApartmentBookedException, ApartmentNotExistsException
	{	
		if (moveInDate.after(moveOutDate))
			throw new DateIncorrectException("MoveIn Date is later then MoveOut Date");		
		
		if (moveInDate.before(Calendar.getInstance().getTime()))
			throw new DateIncorrectException("MoveIn Date has already passed");		
		
		Session session = HibernateUtility.getSessionFactory().openSession();	
		
		try
		{		
		
		Apartment apartment = (Apartment) 
			session.createQuery("from Apartment where apartmentId = :apartmentId")
			.setInteger("apartmentId", apartmentId)
			.uniqueResult();
		
		if (apartment == null)
			throw new ApartmentNotExistsException("Apartment not found");
		
		if (!isApartmentAvailable(apartment.getApartmentId(), moveInDate, moveOutDate, session))
			throw new ApartmentBookedException("Apartment is already booked during stated period");
		
		float bookingCost = DateHelper.countDaysBetween(moveInDate, moveOutDate) * apartment.getDayCostPerPerson();
		
		ApartmentBooking booking = new ApartmentBooking();
		booking.setBookingId(UUID.randomUUID().toString());		
		booking.setBookedFrom(moveInDate);
		booking.setBookedTill(moveOutDate);
		booking.setApartment(apartment); 
		booking.setBookingFee(bookingCost);
		
		chargeBookingCost(accountNumber, bookingCost);
		session.save(booking);
		
		session.flush();
		
		return booking.getBookingId();
		}
		finally
		{
			session.close();
		}
	}

	@WebMethod
	public float cancelApartment(String bookingId, long accountNumber) 
		throws BookingNotFoundException
	{	
		Session session = HibernateUtility.getSessionFactory().openSession();			
		
		try
		{
		ApartmentBooking booking = (ApartmentBooking) 
			session.createQuery("from ApartmentBooking where bookingId = ?")
			.setString(0, bookingId).uniqueResult();
		
		if (booking == null)
		{
			throw new BookingNotFoundException("Booking not found");
		}
		
		float returnAmount = calculateReturnAmount(booking.getBookedFrom(), booking.getBookingFee());
		session.delete(booking);
		returnBookingCost(accountNumber, returnAmount);
		
		session.flush();
		session.close();
		
		return returnAmount;
		}
		finally
		{
			session.close();
		}
	}
	
	@WebMethod
	public float calculateCancellationFee(String bookingId) throws BookingNotFoundException
	{
		Session session = HibernateUtility.getSessionFactory().openSession();		
		
		try
		{
		ApartmentBooking booking = (ApartmentBooking) 
			session.createQuery("from ApartmentBooking where bookingId = ?")
			.setString(0, bookingId).uniqueResult();		
		
		if (booking == null)
			throw new BookingNotFoundException("Booking not found");
		
		return booking.getBookingFee() - calculateReturnAmount(booking.getBookedFrom(), booking.getBookingFee());
		}
		finally
		{
			session.close();
		}
	}
	
	ApartmentBooking getBookingDetails(String bookingId) throws BookingNotFoundException
	{
		Session session = HibernateUtility.getSessionFactory().openSession();

		try {
			ApartmentBooking booking = (ApartmentBooking) session.createQuery(
					"from ApartmentBooking where bookingId = ?").setString(0,
					bookingId).uniqueResult();

			if (booking == null)
				throw new BookingNotFoundException("Booking not found");

			return booking;
		} 
		finally {
			session.close();
		}		
	}		

	private boolean isApartmentAvailable(int apartmentId, Date moveInDate, Date moveOutDate, Session session)
	{
		int count = (Integer) session.createQuery(
			"select count(*) from Apartment a where a.apartmentId = :apartmentId AND a.apartmentId NOT IN(SELECT b.Apartment.apartmentId FROM ApartmentBooking b WHERE (b.bookedFrom BETWEEN :startDate and :endDate) OR (b.bookedTill BETWEEN :startDate and :endDate) OR ((b.bookedFrom < :startDate) AND (b.bookedTill > :endDate)))")
			.setInteger("apartmentId", apartmentId)
			.setDate("startDate", moveInDate)
			.setDate("endDate", moveOutDate)
			.uniqueResult();
		
		if (count > 0)
			return true;
		
		return false;
	}
	
	private void chargeBookingCost(long accountNumber, float bookingCost)
	{
		// TODO Implement Bank communication logic
		BankWSClient.TransferMoney(accountNumber, APARTMENT_PROVIDER_ACCOUNT, bookingCost);
	}
	
	private void returnBookingCost(long accountNumber, float returnAmount)
	{
		// TODO Implement Bank communication logic
		BankWSClient.TransferMoney(APARTMENT_PROVIDER_ACCOUNT, accountNumber, returnAmount);		
	}
	
	private float calculateReturnAmount(Date bookingStartDate, float bookingFee) {
		// TODO Apply Cancellation policy
		return bookingFee;
	}	
}
